use async_std::{
    fs,
    net::{TcpListener, TcpStream},
    task,
};
use async_std::io::{ReadExt, WriteExt};
use futures::stream::StreamExt;
use std::time::Duration;
use std::time::Instant;

#[async_std::main]
async fn main() -> async_std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:7878").await?;

    listener
        .incoming()
        .for_each_concurrent(None, |stream_res| async move {
            match stream_res {
                Ok(stream) => handle_connection(stream).await,
                Err(err) => eprintln!("failed to accept connection: {err}"),
            }
        })
        .await;

    Ok(())
}

async fn handle_connection(mut stream: TcpStream) {
    let started = Instant::now();
    println!(
        "[{:?}] start handling {}",
        started,
        stream.peer_addr().unwrap()
    );

    let mut buffer = [0u8; 1024];

    if stream.read(&mut buffer).await.is_err() {
        return;
    }

    let get = b"GET / HTTP/1.1\r\n";
    let sleep = b"GET /sleep HTTP/1.1\r\n";

    let (status_line, filename) = if buffer.starts_with(get) {
        ("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
    } else if buffer.starts_with(sleep) {
        task::sleep(Duration::from_secs(5)).await;
        ("HTTP/1.1 200 OK\r\n\r\n", "hello.html")
    } else {
        ("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
    };

    let contents = match fs::read_to_string(filename).await {
        Ok(contents) => contents,
        Err(err) => {
            eprintln!("failed to read {filename}: {err}");
            return;
        }
    };

    let response = format!("{status_line}{contents}");

    if stream.write_all(response.as_bytes()).await.is_err() {
        return;
    }

    let _ = stream.flush().await;

    println!(
        "[{:?}] finished handling {} (elapsed {:?})",
        Instant::now(),
        stream.peer_addr().unwrap(),
        started.elapsed(),
    );
}